package belf.migrate.engine.ddl.helper.mysql;

import belf.migrate.api.exception.APIErrorCode;
import belf.migrate.core.exception.BelfRuntimeException;
import belf.migrate.core.jdbc.FunctionModel;
import belf.migrate.core.jdbc.TypeModel;
import belf.migrate.core.util.StringTool;
import belf.migrate.engine.ddl.BaseDDLConvert;
import belf.migrate.engine.job.exception.ConvertViewException;
import belf.migrate.plugin.mysql.antlr.MySqlParser;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 基于antlr解析的mysql的语法树，重构sql语句的辅助类
 */
public class MySqlAntlrBuilder {
    /** sb.append()时，这些符号前不加空格 */
    public static List<String> TRIMLEFT_SYMBOLS = Arrays.asList("'", "`", "\"", ".", ",", ";");
    /** 组合数学符号的前缀 */
    public static List<String> PREFIX_SYMBOLS = Arrays.asList("!", "&", "<", ">", "=");
    /** 组合数学符号的后缀 */
    public static List<String> SUFFIX_SYMBOLS = Arrays.asList("=",">", "<");
    /** 前后需要换行的关键词 */
    public static List<String> LINEBREAK_KEYWORDS = Arrays.asList("AS",
            "IS",
            "FROM",
            "WHERE",
            "ORDER BY",
            "GROUP",
            "BEGIN",
            "END");

    private BaseDDLConvert ddlConvert;

    protected Map<String, FunctionModel> funcMapping;
    protected Map<String, TypeModel> typeMapping;

    private StringBuilder sb;
    /** 标识符默认使用双引号括起来。如果传入的quote参数为null或""，则标识符不需要括起来 */
    private String quote = "\"";

    private String schema;

    public MySqlAntlrBuilder(BaseDDLConvert ddlConvert,
                             StringBuilder sb,
                             String quote,
                             String schema) {
        this.ddlConvert = ddlConvert;
        this.funcMapping = ddlConvert.getFuncMapping();
        this.typeMapping = ddlConvert.getTypeMapping();
        this.sb = sb;

        if (StringUtils.isEmpty(quote)) {
            this.quote = "";
        } else {
            this.quote = quote;
        }

        this.schema = schema;
    }

    public void output(TerminalNode node) {
        String text = node.getText().trim();

        if (TRIMLEFT_SYMBOLS.contains(text)) {
            if (sb.charAt(sb.length() - 1) == ' ') {
                // 删除最后一个字符
                sb.deleteCharAt(sb.length() - 1);
            }
            sb.append(text);
        } else if (PREFIX_SYMBOLS.contains(text)) {
            sb.append(text);
        } else if (SUFFIX_SYMBOLS.contains(text)) {
            if (sb.charAt(sb.length() - 1) == ' ') {
                // 删除最后一个字符
                sb.deleteCharAt(sb.length() - 1);
            }
            sb.append(text);
        } else if (BaseDDLConvert.SQL_KEYWORDS.contains(text.toUpperCase())){
            appendTrimLeft(text);
        } else if (text.equals("(") || text.equals(")")){
            while (sb.charAt(sb.length() - 1) == ' ') {
                sb.deleteCharAt(sb.length() - 1);
            }
            if (sb.charAt(sb.length() - 1) == text.charAt(0)) {
                sb.append(text);
            } else {
                sb.append(" " + text);
            }
        } else if (LINEBREAK_KEYWORDS.contains(text)) {
            sb.append('\n');
            sb.append(text);
            sb.append('\n');
        } else if (node.getText().equalsIgnoreCase("_utf8mb4")) { // 2.忽略mysql的字符串编码转换语法

        } else if (ddlConvert.hasAncestor("FullColumnNameContext", node)) { // 是字段
            if (ddlConvert.hasAncestor("DottedIdContext", node)) { // 字段名称
                text = StringUtils.replace(node.getText(), "`", quote);
                sb.append(text + " ");
            } else if (text.equals(".")) { //中间的点
                sb.append(text);
            } else { //字段名称
                text = StringUtils.replace(node.getText(), "`", quote);
                sb.append(" " + text);
            }
        } else if (ddlConvert.hasAncestor("TableSourcesContext", node)) {// 是表名
            String tableName = StringUtils.replace(node.getText(), "`", quote);
            if (ddlConvert.hasAncestor("TableNameContext", node)) {
                // 找到表名称，需要加上schema
                if (StringUtils.isNotEmpty(schema)) {
                    sb.append(quote + schema + quote + ".");
                }
                sb.append(StringTool.addQuote(tableName, quote) + " ");
            } else { // 是原表名的别名
                sb.append(tableName + " ");
            }
        } else if (ddlConvert.hasAncestor("UidContext", node) &&
                ddlConvert.hasAncestor("SelectElementsContext", node)) {// 是字段别名
            String tableName = StringUtils.replace(node.getText(), "`", quote);
            sb.append(tableName + " ");
        } else if (ddlConvert.hasAncestor("GroupByItemContext", node) ||
                ddlConvert.hasAncestor("OrderByExpressionContext", node) ) {// 是字段别名
            String columnName = StringUtils.replace(node.getText(), "`", quote);
            sb.append(" " + columnName + " ");
        } else if (node.getParent() instanceof MySqlParser.FunctionNameBaseContext ||
                node.getParent() instanceof MySqlParser.ScalarFunctionNameContext ||
                node.getParent() instanceof MySqlParser.UdfFunctionCallContext) {//6.处理各种函数
            // 对于函数，简单地替换为达梦支持的函数名。函数参数及顺序不在这里处理，无法穷尽
            String functionName = text.toUpperCase();

            if (null != funcMapping && funcMapping.containsKey(functionName)) { //函数必须在“db-function-mapping”下的xml文件中定义，否则会出错
                if (funcMapping.get(functionName) != null && StringUtils.isNotEmpty(funcMapping.get(functionName).getTo())) { //有可替换的函数，直接替换
                    text = funcMapping.get(functionName).getTo();
                } else { //无可替换函数，需要报错
                    ddlConvert.addWarning(new ConvertViewException(APIErrorCode.CONVERT_MYSQL_DDL_ERROR,
                            "MySQ的DDL转化，遇到无法自动转化的函数:" + text,
                            ddlConvert.getSql(),
                            text));
                }
            }
            sb.append(' ');
            sb.append(text);
        } else {
            sb.append(' ');
            sb.append(text);
        }
    }

    public void append(String text) {
        sb.append(text);
    }

    public void appendTrimLeft(String text) {
        while (!(sb.isEmpty()) && sb.charAt(sb.length() - 1) == ' ') {
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append(" " + text + " ");
    }

    public char charAt(int i) {
        return sb.charAt(i);
    }

    public int length() {
        return sb.length();
    }

    public void deleteCharAt(int i) {
        sb.deleteCharAt(i);
    }

    public String toSql() {
        return sb.toString();
    }

    public void addError(BelfRuntimeException exception) {
        ddlConvert.addWarning(exception);
    }

}
